package ffte.core.flat

import spinal.core._
import ffte.types.VecComplex
import ffte.property.{getFFTIW,getFFTOW,getShiftMethod,FFTIOConfig}
import ffte.algorithm.FFTGen
import ffte.core.widthed
import ffte.core.streamed.streamedComponents

/**
 * Simple 2-point DFT implementation
 *
 * Implements a specialized 2-point Discrete Fourier Transform using the most
 * basic and efficient hardware implementation. This component is optimized for
 * the special case where N=2, which allows for significant simplification.
 *
 * Mathematical Foundation:
 * For N=2, the DFT simplifies to:
 * X[0] = x[0] + x[1]  (DC component)
 * X[1] = x[0] - x[1]  (Nyquist frequency)
 *
 * Hardware Implementation:
 * - Single-cycle computation (combinatorial logic)
 * - No twiddle factors required for N=2
 * - Fixed-point arithmetic with scaling
 * - Register-based output for timing control
 *
 * Architecture Type: Flat (Non-pipelined)
 * - All computation completes in a single clock cycle
 * - Minimal hardware resource usage
 * - Suitable as building block for larger FFTs
 *
 * @param N Transform size (must be 2)
 * @param S Scaling shift amount for overflow prevention
 *
 * @throws AssertionError when N != 2
 */
class ndft(val N: Int, val S: Int) extends flatComponents {
    // Validate that this is a 2-point DFT
    assert(N == 2, s"simple dft only support 2, but get $N")

    /** Input data width from global configuration */
    val dW = getFFTIW()

    /** Output data width from global configuration */
    val oW = getFFTOW()

    /** Standard I/O interface for flat FFT components */
    val io = iogen

    /**
     * 2-point DFT computation unit
     *
     * Implements the simplified 2-point DFT with extra precision
     * for intermediate calculations and proper scaling for output.
     */
    val u = new dft2(dW, oW + S)

    // Connect input data directly to computation unit
    u.d := io.d

    /**
     * Output data with final scaling
     *
     * The computation unit results are scaled back to the output width
     * using the specified shift amount S. This prevents overflow while
     * maintaining required precision.
     */
    io.q := u.q.fixTo(oW, S)

    // Connect clock enable signal
    u.ce := io.ce

    /**
     * Component delay in clock cycles
     *
     * @return 1 (single-cycle computation)
     */
    def delay = 1
}

/**
 * 2-point DFT computation area
 *
 * Implements the core computation for a 2-point Discrete Fourier Transform.
 * This area contains the actual arithmetic operations and timing control
 * for the simplified 2-point case.
 *
 * Mathematical Operations:
 * X[0] = x[0] + x[1]  (DC component - sum of inputs)
 * X[1] = x[0] - x[1]  (Nyquist component - difference of inputs)
 *
 * Fixed-Point Considerations:
 * - Handles precision differences between input and output
 * - Applies appropriate scaling when output width is smaller
 * - Uses registered output for proper timing control
 * - Prevents overflow through width management
 *
 * Hardware Implementation:
 * - Addition/subtraction operations for complex numbers
 * - Conditional width reduction for output precision
 * - Register-based output synchronized with clock enable
 *
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 */
class dft2(dW: Int, oW: Int) extends Area {
    /**
     * Input data signals
     *
     * Two complex input samples with specified precision.
     * The input width can be different from the output width.
     */
    val d = VecComplex(2, dW)

    /**
     * Output data registers
     *
     * Two complex output samples stored in registers.
     * Using registers provides proper timing control and enables
     * single-cycle computation with registered output.
     */
    val q = Reg(VecComplex(2, oW))

    /**
     * Clock enable signal
     *
     * Controls when the computation should be performed.
     * When high, the DFT computation executes and results are stored.
     */
    val ce = Bool

    /**
     * 2-point DFT computation logic
     *
     * Performs the simplified 2-point DFT when clock enable is asserted.
     * The computation is optimized for fixed-point arithmetic with
     * appropriate width management.
     */
    when(ce) {
        if (oW < dW) {
            /**
             * Width reduction case: Output width is smaller than input
             *
             * When output precision is less than input precision, results
             * must be scaled down to prevent overflow and match output format.
             * No additional scaling (shift = 0) since width reduction provides
             * sufficient precision management.
             */
            q.d(0) := (d.d(0) + d.d(1)).fixTo(oW, 0)  // DC component
            q.d(1) := (d.d(0) - d.d(1)).fixTo(oW, 0)  // Nyquist component
        } else {
            /**
             * Equal or greater output width case
             *
             * When output precision is equal to or greater than input precision,
             * no additional scaling is needed. The natural precision of
             * the addition/subtraction operations is sufficient.
             */
            q.d(0) := d.d(0) + d.d(1)  // DC component
            q.d(1) := d.d(0) - d.d(1)  // Nyquist component
        }
    }
}

/**
 * Factory object for Forward FFT (FFFT) implementations
 *
 * Provides convenient factory methods for creating forward FFT components
 * with automatic scaling and width configuration. This factory simplifies
 * the instantiation of flat FFT components for common use cases.
 *
 * Scaling Strategy:
 * The factory automatically calculates appropriate scaling to prevent
 * overflow in fixed-point arithmetic while maintaining precision.
 * - Base scaling from getShiftMethod(N) for transform size
 * - Additional scaling if input width > output width (dW > oW)
 * - Total scaling ensures safe fixed-point computation
 *
 * Factory Methods:
 * - apply(N): Automatic scaling with global widths
 * - apply(N, S): Custom scaling with global widths
 * - apply(N, S, width): Custom scaling with unified widths
 * - apply(N, S, inWidth, outWidth): Full customization
 *
 * @example
 * // Create 64-point FFT with automatic scaling
 * val fft64 = FFFT(64)
 *
 * // Create 128-point FFT with custom scaling
 * val fft128 = FFFT(128, 8)
 *
 * // Create 32-point FFT with 16-bit unified width
 * val fft32 = FFFT(32, 4, 16)
 */
object FFFT extends widthed {
    /**
     * Create FFT with automatic scaling and global widths
     *
     * @param N FFT transform size
     * @return Forward FFT component with automatic configuration
     */
    def apply(N: Int) = {
        val s = getShiftMethod(N) + (if (dW > oW) (dW - oW) else 0)
        new dft(N, s, dW, oW)
    }

    /**
     * Create FFT with custom scaling and global widths
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @return Forward FFT component with specified scaling
     */
    def apply(N: Int, S: Int) = new dft(N, S, dW, oW)

    /**
     * Create FFT with custom scaling and unified width
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @param _dW Unified data width for both input and output
     * @return Forward FFT component with unified width
     */
    def apply(N: Int, S: Int, _dW: Int) = new dft(N, S, _dW, _dW)

    /**
     * Create FFT with full customization
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @param _dW Input data width
     * @param _oW Output data width
     * @return Forward FFT component with full customization
     */
    def apply(N: Int, S: Int, _dW: Int, _oW: Int) = new dft(N, S, _dW, _oW)
}

/**
 * Factory object for Inverse FFT (FIFFT) implementations
 *
 * Provides convenient factory methods for creating inverse FFT (IFFT) components.
 * The inverse FFT computes the inverse Discrete Fourier Transform, converting
 * frequency domain data back to time domain.
 *
 * Mathematical Foundation:
 * The inverse FFT uses the same computational structure as the forward FFT
 * but with conjugated twiddle factors. In fixed-point arithmetic, this is
 * typically implemented by swapping real and imaginary components and using
 * the same computational kernel.
 *
 * Scaling Considerations:
 * - Same scaling strategy as forward FFT
 * - Output may need additional 1/N scaling depending on convention
 * - Fixed-point precision managed through scaling shifts
 *
 * Factory Methods:
 * Same method signatures as FFFT but with inv=true parameter
 *
 * @example
 * // Create 64-point inverse FFT with automatic scaling
 * val ifft64 = FIFFT(64)
 *
 * // Create 128-point inverse FFT with custom scaling
 * val ifft128 = FIFFT(128, 8)
 */
object FIFFT extends widthed {
    /**
     * Create inverse FFT with automatic scaling and global widths
     *
     * @param N FFT transform size
     * @return Inverse FFT component with automatic configuration
     */
    def apply(N: Int) = {
        val s = getShiftMethod(N) + (if (dW > oW) (dW - oW) else 0)
        new dft(N, s, dW, oW, true)
    }

    /**
     * Create inverse FFT with custom scaling and global widths
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @return Inverse FFT component with specified scaling
     */
    def apply(N: Int, S: Int) = new dft(N, S, dW, oW, true)

    /**
     * Create inverse FFT with custom scaling and unified width
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @param _dW Unified data width for both input and output
     * @return Inverse FFT component with unified width
     */
    def apply(N: Int, S: Int, _dW: Int) = new dft(N, S, _dW, _dW, true)

    /**
     * Create inverse FFT with full customization
     *
     * @param N FFT transform size
     * @param S Custom scaling shift amount
     * @param _dW Input data width
     * @param _oW Output data width
     * @return Inverse FFT component with full customization
     */
    def apply(N: Int, S: Int, _dW: Int, _oW: Int) = new dft(N, S, _dW, _oW, true)
}

/**
 * General-purpose DFT/IDFT implementation component
 *
 * Implements a configurable Discrete Fourier Transform using the flat architecture.
 * This component can handle arbitrary transform sizes and supports both forward
 * and inverse transforms. It uses the FFTE algorithm generation system to
 * create optimal hardware implementations.
 *
 * Architecture Type: Flat (Non-pipelined)
 * - All computation completes in a single clock cycle (combinatorial logic)
 * - No internal pipelining registers
 * - High resource usage but minimal latency
 * - Suitable for applications where latency is critical
 *
 * Algorithm Generation:
 * Uses the FFTE algorithm generation system to create optimal hardware:
 * - Automatic factorization and decomposition selection
 * - Winograd algorithms for prime factors when enabled
 * - Optimal data permutation for hardware efficiency
 * - Hierarchical decomposition for large transforms
 *
 * Transform Modes:
 * - Forward FFT (inv=false): Time domain to frequency domain
 * - Inverse FFT (inv=true): Frequency domain to time domain
 * - Uses same computational kernel with conjugated twiddle factors
 *
 * Fixed-Point Arithmetic:
 * - Configurable input/output bit widths
 * - Automatic scaling to prevent overflow
 * - Precision management through bit allocation
 * - Hardware-optimized arithmetic operations
 *
 * @param N Transform size (number of points)
 * @param S Scaling shift amount for overflow prevention
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 * @param inv Whether to perform inverse transform (true=IFFT, false=FFT)
 *
 * @example
 * // Create 64-point forward FFT with 16-bit I/O and automatic scaling
 * val fft64 = new dft(64, 8, 16, 16, false)
 *
 * // Create 128-point inverse FFT with custom scaling
 * val ifft128 = new dft(128, 12, 20, 16, true)
 *
 * // Create 32-point FFT with 24-bit input, 16-bit output
 * val fft32 = new dft(32, 6, 24, 16, false)
 */
class dft(val N: Int, val S: Int, val dW: Int, val oW: Int, inv: Boolean = false) extends flatComponents {
    /** Standard I/O interface for flat FFT components */
    val io = iogen

    // Use Winograd algorithms for prime factors (better performance)
    FFTGen.Winograd

    /**
     * Generate FFT decomposition structure
     *
     * Creates the optimal decomposition for the specified transform size.
     * Uses reverse generation for better hardware mapping of data flow.
     */
    val gp = FFTGen.rgen[Flat](N)

    /** Flat implementation method with specified scaling */
    val m = new Flat(S)

    /**
     * Hardware implementation area with width-specific configuration
     *
     * This area encapsulates all the data path logic with specific
     * width configurations for optimal hardware mapping.
     */
    val widthArea = FFTIOConfig(dW, oW) on new Area {
        // Get default widths for reference (for debugging/verification)
        val default_dW = getFFTIW()
        val default_oW = getFFTOW()

        /**
         * Generate the actual FFT hardware component
         *
         * Creates the hardware implementation based on the decomposition
         * structure generated above. The Flat method implements the
         * non-pipelined architecture where all computation happens
         * in a single cycle.
         */
        val u = m(gp.fft, "top")

        /**
         * Input data preparation
         *
         * For inverse FFT, swap real and imaginary components to convert
         * between forward and inverse transform conventions. This is a
         * common technique in fixed-point FFT implementations.
         */
        val d = (if (inv) io.d.swap else io.d)

        // Output data buffer with configured width
        val q = VecComplex(N, oW)

        /**
         * Input data permutation and connection
         *
         * Maps input data to the FFT component using the load table
         * generated by the decomposition algorithm. This handles the
         * data reordering required for different FFT algorithms.
         *
         * Each input sample is routed to the appropriate input of the
         * generated FFT component based on the load table.
         */
        for (i <- 0 until N) {
            u.io.d.d(i) := d.d(gp.load_tab(i))
        }

        /**
         * Output data collection and permutation
         *
         * Collects results from the FFT component and reorders them
         * according to the store table to produce the final output.
         *
         * Each output from the FFT component is stored in the
         * appropriate output position based on the store table.
         */
        for (i <- 0 until N) {
            q.d(gp.store_tab(i)) := u.io.q.d(i)
        }

        // Connect clock enable signal to control computation timing
        u.io.ce := io.ce
    }

    /**
     * Final output preparation
     *
     * For inverse FFT, swap real and imaginary components back to
     * the standard format. For forward FFT, pass data through unchanged.
     * This completes the conjugate twiddle factor handling for IFFT.
     */
    io.q := (if (inv) widthArea.q.swap else widthArea.q)

    /**
     * Component delay in clock cycles
     *
     * For flat implementation, this represents the combinatorial
     * delay path through the component. Since all computation
     * happens in a single cycle, the delay is determined by the
     * generated FFT structure.
     *
     * @return Delay in clock cycles from input to output
     */
    def delay = widthArea.u.delay
}

